#include "RealRepresentationEngine.hpp"
#include "BasicRepresentationEngine.hpp"

#define CALLBACK __stdcall

const float RealRepresentationEngine::DOOR_DEPTH = 1.2f;

const float RealRepresentationEngine::CAMERA_DEPTH_STEP = 3.0f;
const float RealRepresentationEngine::DEFAULT_CAMERA_DEPTH = 15.0f;
const float RealRepresentationEngine::MAX_CAMERA_DEPTH = 30.0f;
const float RealRepresentationEngine::MIN_CAMERA_DEPTH = 12.0f;      // This is the depth of the depthest room at the game.

const int RealRepresentationEngine::TEXTURE_BOX = 0;
const int RealRepresentationEngine::TEXTURE_DOOR = 1;
const int RealRepresentationEngine::TEXTURE_FLOOR = 3;
const int RealRepresentationEngine::TEXTURE_GLASS = 4;
const int RealRepresentationEngine::TEXTURE_HERO = 5;
const int RealRepresentationEngine::TEXTURE_MESSAGE = 6;
const int RealRepresentationEngine::TEXTURE_METAL = 7;
const int RealRepresentationEngine::TEXTURE_TITLE = 8;
const int RealRepresentationEngine::TEXTURE_WALL = 9;

const float RealRepresentationEngine::TEXTURE_DOOR_WALL_PROPORTION = 0.25f;
const float RealRepresentationEngine::TEXTURE_FLASH_PROPORTION = 0.5f;
const float RealRepresentationEngine::TEXTURE_PLATFORM_PROPORTION = 0.5f;
const float RealRepresentationEngine::TEXTURE_ROOM_PROPORTION = 0.1f;
const float RealRepresentationEngine::TEXTURE_SWITCH_PROPORTION = 0.25f;

const float RealRepresentationEngine::TEXT_FRAME_MARGIN = 0.01f;
const float RealRepresentationEngine::TEXT_SCALE = 0.001f;

void CALLBACK tessBegin(GLenum which) {
    glBegin(which);
}

void CALLBACK tessCombine(double coords[3], double* data[4], float weight[4], double** result) {
    *result = (double*)malloc(3 * sizeof(double));
    (*result)[0] = coords[0];
    (*result)[1] = coords[1];
    (*result)[2] = coords[2];
}

void CALLBACK tessEnd() {
    glEnd();
}

void CALLBACK tessError(GLenum error) {
    cerr << "Tasselation error: " << gluErrorString(error) << endl;
}

void CALLBACK tessVertex(const GLvoid *data) {
    glVertex3dv((double*)data);
}

RealRepresentationEngine::RealRepresentationEngine(GameEngine* gameEngine) : RepresentationEngine(gameEngine) {
    this->cameraDepth = RealRepresentationEngine::DEFAULT_CAMERA_DEPTH;
    this->currentCameraDepth = this->cameraDepth;

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_NORMALIZE);

    // Light.
    float diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
    float position[] = {0.0f, 0.0f, 0.0f, 1.0f};

    glShadeModel(GL_SMOOTH);
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);

    // Tesselator.
    this->tesselator = gluNewTess();
    gluTessCallback(this->tesselator, GLU_TESS_BEGIN, (_GLUfuncptr)tessBegin);
    gluTessCallback(this->tesselator, GLU_TESS_END, (_GLUfuncptr)tessEnd);
    gluTessCallback(this->tesselator, GLU_TESS_VERTEX, (_GLUfuncptr)tessVertex);
    gluTessCallback(this->tesselator, GLU_TESS_COMBINE, (_GLUfuncptr)tessCombine);
    gluTessCallback(this->tesselator, GLU_TESS_ERROR, (_GLUfuncptr)tessError);

    // Quad.
    this->quad = gluNewQuadric();

    // Fonts.
    this->font = new glfont::GLFont();
    this->font->Create("fonts/menu.glf", 1);

    // Textures.
    glEnable(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexGend(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    glTexGend(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);

    glGenTextures(NUMBER_OF_TEXTURES, this->textures);

    this->loadTexture("textures/box.png", RealRepresentationEngine::TEXTURE_BOX);
    this->loadTexture("textures/door.png", RealRepresentationEngine::TEXTURE_DOOR);
    this->loadTexture("textures/floor.png", RealRepresentationEngine::TEXTURE_FLOOR);
    this->loadTexture("textures/glass.png", RealRepresentationEngine::TEXTURE_GLASS);
    this->loadTexture("textures/hero.png", RealRepresentationEngine::TEXTURE_HERO);
    this->loadTexture("textures/message.png", RealRepresentationEngine::TEXTURE_MESSAGE);
    this->loadTexture("textures/metal.png", RealRepresentationEngine::TEXTURE_METAL);
    this->loadTexture("textures/title.png", RealRepresentationEngine::TEXTURE_TITLE);
    this->loadTexture("textures/wall.png", RealRepresentationEngine::TEXTURE_WALL);

    anim=1;
    seq=delay=0;

}

RealRepresentationEngine::~RealRepresentationEngine() {
    gluDeleteTess(this->tesselator);
    gluDeleteQuadric(this->quad);
    glDeleteTextures(NUMBER_OF_TEXTURES, this->textures);
    this->font->Destroy();
    delete this->font;
}

void RealRepresentationEngine::drawCubeWithAdjustedTexture(float width, float height, float depth, int texture) {
    float textureBounds[3][2] = {
        {0.0f, 1.0f},
        {0.0f, 1.0f},
        {0.0f, 1.0f}
    };

    this->drawCubeWithTexture(width, height, depth, texture, textureBounds);
}

void RealRepresentationEngine::drawCubeWithRepeatedTexture(float width, float height, float depth, int texture, float proportion) {
    float textureBounds[3][2] = {
        {0.0f, width * proportion},
        {0.0f, height * proportion},
        {0.0f, depth * proportion}
    };

    this->drawCubeWithTexture(width, height, depth, texture, textureBounds);
}

void RealRepresentationEngine::drawCubeWithTexture(float width, float height, float depth, int texture, const float textureBounds[3][2]) {
    float midWidth = width / 2.0f;
    float midHeight = height / 2.0f;
    float midDepth = depth / 2.0f;

    glBindTexture(GL_TEXTURE_2D, texture);
    glBegin(GL_QUADS);

    // Front.
    glNormal3f(0.0f, 0.0f, 1.0f);
    glTexCoord2f(textureBounds[0][0], textureBounds[1][0]);
    glVertex3f(-midWidth, midHeight, midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[1][0]);
    glVertex3f(midWidth, midHeight, midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[1][1]);
    glVertex3f(midWidth, -midHeight, midDepth);
    glTexCoord2f(textureBounds[0][0], textureBounds[1][1]);
    glVertex3f(-midWidth, -midHeight, midDepth);

    // Back.
    glNormal3f(0.0f, 0.0f, -1.0f);
    glTexCoord2f(textureBounds[0][0], textureBounds[1][0]);
    glVertex3f(-midWidth, midHeight, -midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[1][0]);
    glVertex3f(midWidth, midHeight, -midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[1][1]);
    glVertex3f(midWidth, -midHeight, -midDepth);
    glTexCoord2f(textureBounds[0][0], textureBounds[1][1]);
    glVertex3f(-midWidth, -midHeight, -midDepth);

    // Left.
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glTexCoord2f(textureBounds[2][0], textureBounds[1][0]);
    glVertex3f(-midWidth, midHeight, -midDepth);
    glTexCoord2f(textureBounds[2][1], textureBounds[1][0]);
    glVertex3f(-midWidth, midHeight, midDepth);
    glTexCoord2f(textureBounds[2][1], textureBounds[1][1]);
    glVertex3f(-midWidth, -midHeight, midDepth);
    glTexCoord2f(textureBounds[2][0], textureBounds[1][1]);
    glVertex3f(-midWidth, -midHeight, -midDepth);

    // Right.
    glNormal3f(1.0f, 0.0f, 0.0f);
    glTexCoord2f(textureBounds[2][0], textureBounds[1][0]);
    glVertex3f(midWidth, midHeight, -midDepth);
    glTexCoord2f(textureBounds[2][1], textureBounds[1][0]);
    glVertex3f(midWidth, midHeight, midDepth);
    glTexCoord2f(textureBounds[2][1], textureBounds[1][1]);
    glVertex3f(midWidth, -midHeight, midDepth);
    glTexCoord2f(textureBounds[2][0], textureBounds[1][1]);
    glVertex3f(midWidth, -midHeight, -midDepth);

    // Top.
    glNormal3f(0.0f, 1.0f, 0.0f);
    glTexCoord2f(textureBounds[0][0], textureBounds[2][0]);
    glVertex3f(-midWidth, midHeight, -midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[2][0]);
    glVertex3f(midWidth, midHeight, -midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[2][1]);
    glVertex3f(midWidth, midHeight, midDepth);
    glTexCoord2f(textureBounds[0][0], textureBounds[2][1]);
    glVertex3f(-midWidth, midHeight, midDepth);

    // Bottom.
    glNormal3f(0.0f, -1.0f, 0.0f);
    glTexCoord2f(textureBounds[0][0], textureBounds[2][0]);
    glVertex3f(-midWidth, -midHeight, -midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[2][0]);
    glVertex3f(midWidth, -midHeight, -midDepth);
    glTexCoord2f(textureBounds[0][1], textureBounds[2][1]);
    glVertex3f(midWidth, -midHeight, midDepth);
    glTexCoord2f(textureBounds[0][0], textureBounds[2][1]);
    glVertex3f(-midWidth, -midHeight, midDepth);

    glEnd();

    glBindTexture(GL_TEXTURE_2D, 0);
}

void RealRepresentationEngine::getLightColor(LightElement* lightElement, float* color) {
    if (lightElement->lightType == LightElement::attractiveLight) {
        color[0] = 1.0f;
        color[1] = 0.0f;
        color[2] = 0.0f;
    }
    else if (lightElement->lightType == LightElement::repulsiveLight) {
        color[0] = 0.0f;
        color[1] = 0.0f;
        color[2] = 1.0f;
    }
    else if (lightElement->lightType == LightElement::elasticLight) {
        color[0] = 1.0f;
        color[1] = 0.0f;
        color[2] = 1.0f;
    }
    else if (lightElement->lightType == LightElement::slipperyLight) {
        color[0] = 1.0f;
        color[1] = 1.0f;
        color[2] = 0.0f;
    }
}

void RealRepresentationEngine::init(Menu* menu) {
}

void RealRepresentationEngine::init(Scene* scene) {
}

void RealRepresentationEngine::loadTexture(string filePath, int index) {
    vector<unsigned char> buffer;
    LodePNG::loadFile(buffer, filePath);

    LodePNG::Decoder decoder;
    vector<unsigned char> image;
    decoder.decode(image, buffer);


    if (decoder.hasError()) {
        cerr << "Error loading texture " << filePath << ": " << LodePNG_error_text(decoder.getError()) << endl;
    }
    else {
        glBindTexture(GL_TEXTURE_2D, this->textures[index]);
        gluBuild2DMipmaps(GL_TEXTURE_2D, 4, decoder.getWidth(), decoder.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, &image[0]);
    }
}

void RealRepresentationEngine::render(Menu* menu, Scene* scene) {
    if (scene == NULL) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    }
    else {
        this->render(scene, false);
    }

    glLoadIdentity();

    if (menu->isMainMenu()) {
        glPushMatrix();
        glTranslatef(0.0f, 0.0f, -15.0f);
        glRotatef(10.0f, 0.0f, 1.0f, 0.0f);

        // Drawing the background wall.
        glColor3f(1.0f, 1.0f, 1.0f);
        glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_WALL]);

        glBegin(GL_QUADS);

        glNormal3f(0.0f, 0.0f, 1.0f);

        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-15.0f, 15.0f, 0.0f);

        glTexCoord2f(30.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION, 0.0f);
        glVertex3f(15.0f, 15.0f, 0.0f);

        glTexCoord2f(30.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION, 30.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION);
        glVertex3f(15.0f, -15.0f, 0.0f);

        glTexCoord2f(0.0f, 30.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION);
        glVertex3f(-15.0f, -15.0f, 0.0f);

        glEnd();

        // Drawing the title.
        glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_TITLE]);

        glBegin(GL_QUADS);

        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(0.0f, 9.0f, 0.1f);

        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(9.0f, 9.0f, 0.1f);

        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(9.0f, 0.0f, 0.1f);

        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(0.0f, 0.0f, 0.1f);

        glEnd();

        glBindTexture(GL_TEXTURE_2D, 0);
        glPopMatrix();
    }

    glDisable(GL_LIGHTING);

    // Drawing the background.
    int numberOfOptions = menu->options->size();
    float backgroundWidth = 0;

    for (int i = numberOfOptions - 1; i >= 0; --i) {
        Menu::MenuEntry* entry = menu->options->at(i);
        pair<int, int> size;
        this->font->GetStringSize(entry->label->c_str(), &size);
        float textWidth = size.first * RealRepresentationEngine::TEXT_SCALE;

        if (textWidth > backgroundWidth) {
            backgroundWidth = textWidth;
        }
    }

    backgroundWidth += 2.0f * RealRepresentationEngine::TEXT_FRAME_MARGIN;
    pair<int, int> size;
    this->font->GetStringSize("|", &size);
    float textHeight = size.second * RealRepresentationEngine::TEXT_SCALE;
    float backgroundHeight = 2.0f * RealRepresentationEngine::TEXT_FRAME_MARGIN + numberOfOptions * textHeight;

    glTranslatef(-backgroundWidth / 2.0f, -backgroundHeight / 2.0f, -1.0f);
    glColor4f(0.0f, 0.0f, 0.0f, 0.5f);

    glBegin(GL_QUADS);
    glVertex3f(0.0f, 0.0f, -0.001f);
    glVertex3f(0.0f, backgroundHeight, -0.001f);
    glVertex3f(backgroundWidth, backgroundHeight, -0.001f);
    glVertex3f(backgroundWidth, 0.0f, -0.001f);
    glEnd();

    // Drawing text.
    this->font->Begin();

    for (int i = numberOfOptions - 1; i >= 0; --i) {
        if (i == menu->selectedOption) {
            glColor3f(1.0f, 1.0f, 1.0f);
        }
        else {
            glColor3f(0.8f, 0.8f, 0.8f);
        }

        Menu::MenuEntry* entry = menu->options->at(i);
        this->font->DrawString(entry->label->c_str(), RealRepresentationEngine::TEXT_SCALE, RealRepresentationEngine::TEXT_FRAME_MARGIN, RealRepresentationEngine::TEXT_FRAME_MARGIN + (numberOfOptions - i) * textHeight);
    }

    glBindTexture(GL_TEXTURE_2D, 0);
    glEnable(GL_LIGHTING);

    glutSwapBuffers();
}

void RealRepresentationEngine::render(Scene* scene) {
    this->render(scene, true);
}

void RealRepresentationEngine::render(Scene* scene, bool swapBuffers) {
    this->currentScene = scene;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    // Representing elements.
    b2Vec2 heroPosition = scene->hero->body->GetPosition();
    this->currentCameraDepth += (this->cameraDepth - this->currentCameraDepth) * 0.1f;
    glTranslatef(-heroPosition.x, -heroPosition.y - this->currentCameraDepth * 0.1f, -this->currentCameraDepth);

    vector<Element*> lights;
    vector<Element*> platforms;

    for (vector<Element*>::iterator i = scene->elements->begin(); i != scene->elements->end(); ++i) {
        Element* element = *i;

        if (element->isSpotlight()) {
            SpotlightElement* spotlight = (SpotlightElement*)element;
            this->represent(spotlight);

            if (spotlight->isActuated()) {
                lights.push_back(spotlight->light);
            }
        }
        else if (element->isPlatform()) {
            platforms.push_back(element);
        }
        else if (!element->isHero()) {
            this->represent(element);
        }
    }

    this->represent(scene->hero);

    for (vector<Element*>::iterator i = platforms.begin(); i != platforms.end(); ++i) {
        this->represent(*i);
    }

    for (vector<Element*>::iterator i = lights.begin(); i != lights.end(); ++i) {
        this->represent(*i);
    }

    glLoadIdentity();
    this->representMessage(scene->message);

    if (swapBuffers) {
        glutSwapBuffers();
    }
}

void RealRepresentationEngine::represent(Element* element) {
    glPushMatrix();
    b2Vec2 position = element->body->GetPosition();
    glTranslatef(position.x, position.y, 0.0f);

    if (!element->isSpotlight()) {
        // The spotlight sets its own angle.
        glRotatef(element->body->GetAngle() * 180.0f / b2_pi, 0, 0, 1.0f);
    }

    if (element->isRoom()) {
        this->representRoom((RoomElement*)element);
    }
    else if (element->isHero()) {
        this->representHero((HeroElement*)element);

    }
    else if (element->isBox()) {
        this->representBox((BoxElement*)element);
    }
    else if (element->isSwitch()) {
        this->representSwitch((SwitchElement*)element);
    }
    else if (element->isDoor()) {
        this->representDoor((DoorElement*)element);
    }
    else if (element->isPlatform()) {
        this->representPlatform((PlatformElement*)element);
    }
    else if (element->isSpotlight()) {
        this->representSpotlight((SpotlightElement*)element);
    }
    else if (element->isLight()) {
        this->representLight((LightElement*)element);
    }
    else if (element->isFlash()) {
        this->representFlash((FlashElement*)element);
    }

    glPopMatrix();
}

void RealRepresentationEngine::representBox(BoxElement* boxElement) {
    glColor3f(1.0f, 1.0f, 1.0f);
    this->drawCubeWithAdjustedTexture(1.0f, 1.0f, 1.0f, this->textures[RealRepresentationEngine::TEXTURE_BOX]);
}

void RealRepresentationEngine::representDoor(DoorElement* doorElement) {
    glColor3f(1.0f, 1.0f, 1.0f);

    if (doorElement->wallDirection != 0.0f) {
        glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_METAL]);

        glBegin(GL_QUADS);
        glNormal3f(0.0f, 0.0f, 1.0f);

        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(0.0f, 1.0f, this->currentScene->roomDepth / 2.0f);

        glTexCoord2f(2.0f * RealRepresentationEngine::TEXTURE_DOOR_WALL_PROPORTION, 0.0f);
        glVertex3f(0.0f, -1.0f, this->currentScene->roomDepth / 2.0f);

        glTexCoord2f(2.0f * RealRepresentationEngine::TEXTURE_DOOR_WALL_PROPORTION, 2.0f * RealRepresentationEngine::TEXTURE_DOOR_WALL_PROPORTION);
        glVertex3f(2.0f * doorElement->wallDirection, -1.0f, this->currentScene->roomDepth / 2.0f);

        glTexCoord2f(0.0f, 2.0f * RealRepresentationEngine::TEXTURE_DOOR_WALL_PROPORTION);
        glVertex3f(2.0f * doorElement->wallDirection, 1.0f, this->currentScene->roomDepth / 2.0f);

        glEnd();
    }

    glTranslatef(0.025f * doorElement->wallDirection, 0.0f, 0.0f);
    float depth = (this->currentScene->roomDepth - RealRepresentationEngine::DOOR_DEPTH) / 2.0f;

    glPushMatrix();
    glTranslatef(0.0f, 0.0f, (RealRepresentationEngine::DOOR_DEPTH + depth) / 2.0f);
    this->drawCubeWithRepeatedTexture(0.05f, 2.0f, depth, this->textures[RealRepresentationEngine::TEXTURE_METAL], RealRepresentationEngine::TEXTURE_DOOR_WALL_PROPORTION);
    glPopMatrix();

    glPushMatrix();
    glTranslatef(0.0f, 0.0f, -(RealRepresentationEngine::DOOR_DEPTH + depth) / 2.0f);
    this->drawCubeWithRepeatedTexture(0.05f, 2.0f, depth, this->textures[RealRepresentationEngine::TEXTURE_METAL], RealRepresentationEngine::TEXTURE_DOOR_WALL_PROPORTION);
    glPopMatrix();

    if (!doorElement->isActuated()) {
        glTranslatef(0.0f, 0.0f, -RealRepresentationEngine::DOOR_DEPTH / 2.0f);
        glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
        glTranslatef(0.0f, 0.0f, RealRepresentationEngine::DOOR_DEPTH / 2.0f + 0.03f);
    }

    float textureBounds[3][2] = {
        {0.27f, 0.29f},
        {0.0f, 1.0f},
        {0.25f, 0.77f}
    };

    this->drawCubeWithTexture(0.05f, 2.0f, RealRepresentationEngine::DOOR_DEPTH, this->textures[RealRepresentationEngine::TEXTURE_DOOR], textureBounds);
}

void RealRepresentationEngine::representFlash(FlashElement* flashElement) {
    float impulseAngle = atan2(flashElement->impulse->y, flashElement->impulse->x) - flashElement->body->GetAngle();
    float sin = sinf(impulseAngle + b2_pi / 2.0f);
    float y1 = 0.026 + (1.0f + sin) * 0.1f;
    float y2 = 0.026 + (1.0f - sin) * 0.1f;

    glColor3f(0.1f, 0.1f, 0.1f);
    glBegin(GL_QUADS);

    // Front.
    glNormal3f(0.0f, 0.0f, 1.0f);
    glVertex3f(-0.5f, 0.0f, 0.5f);
    glVertex3f(0.5f, 0.0f, 0.5f);
    glVertex3f(0.5f, y2, 0.5f);
    glVertex3f(-0.5f, y1, 0.5f);

    // Back.
    glNormal3f(0.0f, 0.0f, -1.0f);
    glVertex3f(-0.5f, 0.0f, -0.5f);
    glVertex3f(0.5f, 0.0f, -0.5f);
    glVertex3f(0.5f, y2, -0.5f);
    glVertex3f(-0.5f, y1, -0.5f);

    // Left.
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glVertex3f(-0.5f, y1, 0.5f);
    glVertex3f(-0.5f, y1, -0.5f);
    glVertex3f(-0.5f, 0.0f, -0.5f);
    glVertex3f(-0.5f, 0.0f, 0.5f);

    // Right.
    glNormal3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.5f, y2, 0.5f);
    glVertex3f(0.5f, y2, -0.5f);
    glVertex3f(0.5f, 0.0f, -0.5f);
    glVertex3f(0.5f, 0.0f, 0.5f);

    // Bottom.
    glNormal3f(0.0f, -1.0f, 0.0f);
    glVertex3f(-0.5f, 0.01f, 0.5f);
    glVertex3f(-0.5f, 0.01f, -0.5f);
    glVertex3f(0.5f, 0.01f, -0.5f);
    glVertex3f(0.5f, 0.01f, 0.5f);

    glEnd();

    // Top.
    glColor3f(0.0f, 0.5f, 1.0f);
    glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_GLASS]);

    glBegin(GL_QUADS);

    glNormal3f(0.0f, 1.0f, 0.0f);

    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-0.5f, y1, 0.5f);

    glTexCoord2f(RealRepresentationEngine::TEXTURE_FLASH_PROPORTION, 0.0f);
    glVertex3f(-0.5f, y1, -0.5f);

    glTexCoord2f(RealRepresentationEngine::TEXTURE_FLASH_PROPORTION, RealRepresentationEngine::TEXTURE_FLASH_PROPORTION);
    glVertex3f(0.5f, y2, -0.5f);

    glTexCoord2f(0.0f, RealRepresentationEngine::TEXTURE_FLASH_PROPORTION);
    glVertex3f(0.5f, y2, 0.5f);

    glEnd();

    glBindTexture(GL_TEXTURE_2D, 0);
}

void flipTexture() {
    //glPushMatrix();
    //glMatrixMode(GL_TEXTURE);
    glScalef(-1,1,1);
    //glPopMatrix();
}

void RealRepresentationEngine::drawHero(HeroElement* heroElement,int pos){
    float sx0,sx1;
    sx0 = SPRITE_X_2;
    sx1 = SPRITE_X_3;
    float sy0,sy1;

  /*  if (pos == 1) {
        if(heroElement->lookingAtRight) {
            sy0 = 0;
            sy1 = SPRITE_Y_0;
            flipTexture();
        }
        else {
            sy0 = 0;
            sy1 = SPRITE_Y_0;
        }
    }
    else if(pos == 2){

        if(heroElement->lookingAtRight) {
            sy0 = 0;
            sy1 = SPRITE_Y_1;
            flipTexture();
        }
        else {
            sy0 = 0;
            sy1 = SPRITE_Y_1;
        }
    }*/
    cout << "HERO: " << heroElement->stopping << endl;
    switch(seq)
    {
        case 0:

            if (heroElement->lookingAtRight){
                sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;
                if(heroElement->stopping) {sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
                flipTexture();
            }
            else {

                sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;
                if(heroElement->stopping){ sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
            }

            if (heroElement->onAir) {
                sx0 = SPRITE_X_0;
                sx1 = SPRITE_X_1;
            }
            break;
        case 1:
            if (heroElement->lookingAtRight){
                sy0=SPRITE_Y_1; sy1=SPRITE_Y_2;
                if(heroElement->stopping){ sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
                flipTexture();
            }
            else{

                sy0=SPRITE_Y_1; sy1=SPRITE_Y_2;
                if(heroElement->stopping){ sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
            }
            if (heroElement->onAir) {
                sx0 = SPRITE_X_1;
                sx1 = SPRITE_X_2;
            }
            break;
        case 2:
            if (heroElement->lookingAtRight){
                sy0=SPRITE_Y_2; sy1=SPRITE_Y_3;
                if(heroElement->stopping){ sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
                flipTexture();
            }
            else{

                sy0=SPRITE_Y_2; sy1=SPRITE_Y_3;
                if(heroElement->stopping){ sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
            }
            if (heroElement->onAir) {
                sx0 = SPRITE_X_2;
                sx1 = SPRITE_X_3;
            }
            break;
        case 3:
            if (heroElement->lookingAtRight){
                sy0=SPRITE_Y_3; sy1=SPRITE_Y_4;
                 if(heroElement->stopping){ sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
                flipTexture();
            }
            else {
                sy0=SPRITE_Y_3; sy1=SPRITE_Y_4;
                if(heroElement->stopping){ sy0=SPRITE_Y_0; sy1=SPRITE_Y_1;}
            }
            if (heroElement->onAir) {
                sx0 = SPRITE_X_3;
                sx1 = SPRITE_X_4;
            }
            break;
    }

    delay++;
    if(delay>30)
    {
        if(anim>0)
        {
            seq++;
            if(seq>3)
            {
                anim=-1;
                seq=2;
            }
        }
        else
        {
            seq--;
            if(seq<0)
            {
                anim=1;
                seq=1;
            }
        }

        delay=0;
    }


    glColor3f(1.0f, 1.0f, 1.0f);
    glBindTexture(GL_TEXTURE_2D, textures[RealRepresentationEngine::TEXTURE_HERO]);


    glBegin (GL_QUADS);
    glNormal3f(0.0f, 0.0f, 1.0f);

    glTexCoord2f(sx1, sy1);
    glVertex3f(0.3f, -0.9f, 0.0f);

    glTexCoord2f(sx0, sy1);
    glVertex3f(-0.3f, -0.9f, 0.0f);

    glTexCoord2f(sx0, sy0);
    glVertex3f(-0.3f, 1.0f, 0.0f);

    glTexCoord2f(sx1, sy0);
    glVertex3f(0.3f, 1.0f, 0.0f);

    glEnd();

    glBindTexture(GL_TEXTURE_2D, 0);
}

void RealRepresentationEngine::representHero(HeroElement* heroElement) {
    /***Basic***
    glColor3f(0.0f, 1.0f, 0.0f);
    glScalef(HeroElement::WIDTH, HeroElement::HEIGHT, HeroElement::WIDTH);
    glutSolidCube(1.0f);




    return;         // VUELVO A HABILITAR EL BASIC PARA QUE RULE EL PROGRAMA





    /***Basic***/
/*
    b2Fixture* fixture = heroElement->body->GetFixtureList();
    b2EdgeShape* edgeShape = (b2EdgeShape*)fixture->GetShape();

*/

    drawHero(heroElement,1);
   // drawHero(heroElement, 2);

}

void RealRepresentationEngine::representLight(LightElement* lightElement) {
    glDisable(GL_LIGHTING);

    float color[3];
    this->getLightColor(lightElement, color);
    glColor4f(color[0], color[1], color[2], 0.15f);

    glTranslatef(0.35f, 0.0f, 0.0f);
    glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    gluCylinder(this->quad, 0.25f, lightElement->base, lightElement->length, 50, 1);

    glEnable(GL_LIGHTING);
}

void RealRepresentationEngine::representMessage(string* message) {
    if (message == NULL) {
        return;
    }

    glDisable(GL_LIGHTING);

    // Drawing the background.
    pair<int, int> size;
    this->font->GetStringSize(message->c_str(), &size);
    float backgroundWidth = size.first * RealRepresentationEngine::TEXT_SCALE + 2.0f * RealRepresentationEngine::TEXT_FRAME_MARGIN;
    float textHeight = size.second * RealRepresentationEngine::TEXT_SCALE;
    float backgroundHeight = textHeight + 2.0f * RealRepresentationEngine::TEXT_FRAME_MARGIN;

    glTranslatef(-backgroundWidth / 2.0f, -0.35f, -1.0f);
    glColor4f(0.0f, 0.0f, 0.0f, 0.5f);

    glBegin(GL_QUADS);
    glVertex3f(0.0f, 0.0f, -0.001f);
    glVertex3f(0.0f, backgroundHeight, -0.001f);
    glVertex3f(backgroundWidth, backgroundHeight, -0.001f);
    glVertex3f(backgroundWidth, 0.0f, -0.001f);
    glEnd();

    // Drawing text.
    this->font->Begin();
    glColor3f(1.0f, 1.0f, 1.0f);
    this->font->DrawString(message->c_str(), RealRepresentationEngine::TEXT_SCALE, RealRepresentationEngine::TEXT_FRAME_MARGIN, RealRepresentationEngine::TEXT_FRAME_MARGIN + textHeight);

    glBindTexture(GL_TEXTURE_2D, 0);
    glEnable(GL_LIGHTING);
}

void RealRepresentationEngine::representPlatform(PlatformElement* platformElement) {
    glColor3f(0.0f, 1.0f, 1.0f);

    b2Fixture* fixture = platformElement->body->GetFixtureList();
    b2ChainShape* chainShape = (b2ChainShape*)fixture->GetShape();

    int edgeCount = chainShape->GetChildCount();
    b2EdgeShape edgeShape;

    for (int i = 0; i < edgeCount; ++i) {
        chainShape->GetChildEdge(&edgeShape, i);
        b2Vec2 size(edgeShape.m_vertex2.x - edgeShape.m_vertex1.x, edgeShape.m_vertex2.y - edgeShape.m_vertex1.y);

        glPushMatrix();
        glTranslatef(edgeShape.m_vertex1.x + size.x / 2.0f, edgeShape.m_vertex1.y + size.y / 2.0f, 0.0f);
        glRotatef(atan2(size.y, size.x) * 180.0f / b2_pi, 0.0f, 0.0f, 1.0f);
        this->drawCubeWithRepeatedTexture(size.Length(), 0.05f, this->currentScene->roomDepth, this->textures[RealRepresentationEngine::TEXTURE_GLASS], RealRepresentationEngine::TEXTURE_PLATFORM_PROPORTION);
        glPopMatrix();
    }
}

void RealRepresentationEngine::representRoom(RoomElement* roomElement) {
    glColor3f(1.0f, 1.0f, 1.0f);

    b2Fixture* fixture = roomElement->body->GetFixtureList();
    b2ChainShape* chainShape = (b2ChainShape*)fixture->GetShape();
    int edgeCount = chainShape->GetChildCount();
    b2EdgeShape edgeShape;

    // Drawing the edge walls.
    glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_FLOOR]);
    glBegin(GL_QUADS);
    float lastNormalY = 1.0f;

    for (int i = 0; i < edgeCount; ++i) {
        chainShape->GetChildEdge(&edgeShape, i);
        float normalY = edgeShape.m_vertex2.x - edgeShape.m_vertex1.x;

        if (normalY * lastNormalY <= 0.0f) {
            glEnd();

            if (normalY > 0.0f) {
                glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_FLOOR]);
            }
            else {
                glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_WALL]);
            }

            glBegin(GL_QUADS);
            lastNormalY = normalY;
        }

        glNormal3f(edgeShape.m_vertex1.y - edgeShape.m_vertex2.y, normalY, 0.0f);
        float length = b2Vec2(edgeShape.m_vertex2.x - edgeShape.m_vertex1.x, edgeShape.m_vertex2.y - edgeShape.m_vertex1.y).Length() * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION;

        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(edgeShape.m_vertex1.x, edgeShape.m_vertex1.y, this->currentScene->roomDepth / 2.0f);

        glTexCoord2f(this->currentScene->roomDepth * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION, 0.0f);
        glVertex3f(edgeShape.m_vertex1.x, edgeShape.m_vertex1.y, -this->currentScene->roomDepth / 2.0f);

        glTexCoord2f(this->currentScene->roomDepth * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION, length);
        glVertex3f(edgeShape.m_vertex2.x, edgeShape.m_vertex2.y, -this->currentScene->roomDepth / 2.0f);

        glTexCoord2f(0.0f, length);
        glVertex3f(edgeShape.m_vertex2.x, edgeShape.m_vertex2.y, this->currentScene->roomDepth / 2.0f);
    }

    // Drawing the back wall.

    if (lastNormalY > 0.0f) {
        glEnd();
        glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_WALL]);
        glBegin(GL_QUADS);
    }

    glNormal3f(0.0f, 0.0f, 1.0f);

    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-18.0f, 18.0f, -this->currentScene->roomDepth / 2.0f);

    glTexCoord2f(36.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION, 0.0f);
    glVertex3f(18.0f, 18.0f, -this->currentScene->roomDepth / 2.0f);

    glTexCoord2f(36.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION, 36.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION);
    glVertex3f(18.0f, -18.0f, -this->currentScene->roomDepth / 2.0f);

    glTexCoord2f(0.0f, 36.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION);
    glVertex3f(-18.0f, -18.0f, -this->currentScene->roomDepth / 2.0f);

    glEnd();

    // Drawing the front wall.
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glMatrixMode(GL_TEXTURE);
    glScalef(1.0f / (100.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION), 1.0f / (100.0f * RealRepresentationEngine::TEXTURE_ROOM_PROPORTION), 1.0f);
    glMatrixMode(GL_MODELVIEW);

    double contour[4][3] = {
        {-50.0f, -50.0f, this->currentScene->roomDepth / 2.0f},
        {50.0f, -50.0f, this->currentScene->roomDepth / 2.0f},
        {50.0f, 50.0f, this->currentScene->roomDepth / 2.0f},
        {-50.0f, 50.0f, this->currentScene->roomDepth / 2.0f}
    };

    gluTessBeginPolygon(this->tesselator, NULL);
    gluTessBeginContour(this->tesselator);

    for (int i = 0; i < 4; ++i) {
        gluTessVertex(this->tesselator, contour[i], contour[i]);
    }

    gluTessEndContour(this->tesselator);
    gluTessBeginContour(this->tesselator);
    double hole[edgeCount][3];

    for (int i = 0; i < edgeCount; ++i) {
        chainShape->GetChildEdge(&edgeShape, i);
        hole[i][0] = edgeShape.m_vertex1.x;
        hole[i][1] = edgeShape.m_vertex1.y;
        hole[i][2] = this->currentScene->roomDepth / 2.0f;
        gluTessVertex(this->tesselator, hole[i], hole[i]);
    }

    double vertex[3] = {edgeShape.m_vertex2.x, edgeShape.m_vertex2.y, this->currentScene->roomDepth / 2.0f};
    gluTessVertex(this->tesselator, vertex, vertex);

    gluTessEndContour(this->tesselator);
    gluTessEndPolygon(this->tesselator);

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);

    // Last screen background.

    if (roomElement->roomNumber == 16) {
        glBindTexture(GL_TEXTURE_2D, this->textures[RealRepresentationEngine::TEXTURE_MESSAGE]);
        glBegin(GL_QUADS);

        glNormal3f(0.0f, 0.0f, 1.0f);

        glTexCoord2f(0.0f, 0.0f);
        glVertex3f(-3.0f, 3.0f, -this->currentScene->roomDepth / 2.0f + 0.01f);

        glTexCoord2f(1.0f, 0.0f);
        glVertex3f(3.0f, 3.0f, -this->currentScene->roomDepth / 2.0f + 0.01f);

        glTexCoord2f(1.0f, 1.0f);
        glVertex3f(3.0f, -3.0f, -this->currentScene->roomDepth / 2.0f + 0.01f);

        glTexCoord2f(0.0f, 1.0f);
        glVertex3f(-3.0f, -3.0f, -this->currentScene->roomDepth / 2.0f + 0.01f);

        glEnd();
    }

    glBindTexture(GL_TEXTURE_2D, 0);
}

void RealRepresentationEngine::representSpotlight(SpotlightElement* spotlight) {
    glColor3f(0.1f, 0.1f, 0.1f);

    // Back support bar.
    glPushMatrix();
    glTranslatef(0.0f, 0.0f, -this->currentScene->roomDepth / 4.0f);
    glScalef(0.1f, 0.1f, this->currentScene->roomDepth / 2.0f);
    glutSolidCube(1.0f);
    glPopMatrix();

    // Front support bar.
    glPushMatrix();
    glTranslatef(0.0f, 0.0f, this->currentScene->roomDepth / 8.0f);

    glPushMatrix();
    glScalef(0.1f, 0.1f, this->currentScene->roomDepth / 4.0f);
    glutSolidCube(1.0f);
    glPopMatrix();

    glRotatef(atan2(spotlight->supportBarDirection->y, spotlight->supportBarDirection->x) * 180.0f / b2_pi, 0.0f, 0.0f, 1.0f);
    glTranslatef(7.5f, 0.0f, this->currentScene->roomDepth / 8.0f - 0.05f);
    glScalef(15.0f, 0.1f, 0.1f);
    glutSolidCube(1.0f);
    glPopMatrix();

    // Body.
    glRotatef(spotlight->body->GetAngle() * 180.0f / b2_pi, 0, 0, 1.0f);

    glPushMatrix();
    glTranslatef(-0.35f, 0.0f, 0.0f);
    glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    gluCylinder(this->quad, 0.25f, 0.25f, 0.7f, 10, 1);
    glPopMatrix();

    // Back disk.
    glPushMatrix();
    glTranslatef(-0.35f, 0.0f, 0.0f);
    glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    gluDisk(this->quad, 0.0f, 0.25f, 10, 1);
    glPopMatrix();

    // Front glass.
    float color[3];
    this->getLightColor(spotlight->light, color);
    glColor3f(color[0], color[1], color[2]);

    if (spotlight->isActuated()) {
        glDisable(GL_LIGHTING);
    }

    glTranslatef(0.3f, 0.0f, 0.0f);
    glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    gluDisk(this->quad, 0.0f, 0.25f, 10, 1);

    glEnable(GL_LIGHTING);
}

void RealRepresentationEngine::representSwitch(SwitchElement* switchElement) {
    float height = (switchElement->numberOfContacts > 0) ? 0.026f : 0.1f;
    glTranslatef(0.0f, height / 2.0f, 0.0f);
    glColor3f(1.0f, 0.0f, 0.0f);
    this->drawCubeWithRepeatedTexture(1.5f, height, 1.5f, this->textures[RealRepresentationEngine::TEXTURE_METAL], RealRepresentationEngine::TEXTURE_SWITCH_PROPORTION);
}

void RealRepresentationEngine::zoomIn() {
    this->cameraDepth -= RealRepresentationEngine::CAMERA_DEPTH_STEP;

   if (this->cameraDepth < RealRepresentationEngine::MIN_CAMERA_DEPTH) {
        this->cameraDepth = RealRepresentationEngine::MIN_CAMERA_DEPTH;
    }
}

void RealRepresentationEngine::zoomOut() {
    this->cameraDepth += RealRepresentationEngine::CAMERA_DEPTH_STEP;

    if (this->cameraDepth > RealRepresentationEngine::MAX_CAMERA_DEPTH) {
        this->cameraDepth = RealRepresentationEngine::MAX_CAMERA_DEPTH;
    }
}
